﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using ScrumTable.BL.DM.DataManagement;
using ScrumTable.Common.Collections;
using ScrumTable.Common.Monitoring;
using ScrumTable.Common.Security;
using ScrumTable.Common.Test;
using ScrumTable.Common.Threading;
using ScrumTable.DL.Data;
using ScrumTable.DL.Data.Common;
using ScrumTable.DL.Data.Local;
using ScrumTable.DL.Data.Local.Test;
using ScrumTable.DL.Data.Schema;
using ScrumTable.DL.Data.Schema.ScrumTableV1;

#endregion

namespace ScrumTable.BL.DM.Test.DataManagement
{
    /// <summary>
    /// This is the test class for Iteration class.
    /// </summary>
    [TestClass]
    public abstract class DomainBaseTest<T, TSchema>
        where T : DomainBase, new()
        where TSchema : SchemaBase
    {
        #region Declarations
        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------

        #endregion

        #region Properties
        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext { get; set; }

        /// <summary>
        /// Gets the underlying domain context.
        /// </summary>
        protected IDomainContext Context
        {
            get; private set;
        }

        /// <summary>
        /// Gets the underlying data object which is behind the  object ToTest.
        /// </summary>
        protected IDataObject DataObject { get; private set; }

        /// <summary>
        /// Gets the schema of the underlying data object which is behind the object ToTest.
        /// </summary>
        protected TSchema DataObjectSchema { get; private set; }

        /// <summary>
        /// Gets the default instance to test.
        /// </summary>
        protected abstract T ToTest { get; }

        /// <summary>
        /// Gets the underlying data context instance.
        /// </summary>
        protected IDataContext DataContext { get; private set; }

        #endregion

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        // [TestInitialize()]
        // public void MyTestInitialize() { }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //
        #endregion

        #region Constructors / Destructor
        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Creates a new DomainBaseTest instance.
        /// </summary>
        protected DomainBaseTest()
        {
        }

        #endregion

        #region Methods
        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------

        [TestMethod]
        public void TestIdChain()
        {
            Assert.IsFalse(ValueChain<string>.IsNullOrEmpty(ToTest.IdChain));
        }

        [TestMethod]
        public void TestName()
        {
            Assert.IsNotNull(ToTest.Name);
        }

        [TestMethod]
        public void TestDomainContext()
        {
            Assert.IsNotNull(ToTest.Context);
        }

        [TestMethod]
        public void TestId()
        {
            Assert.IsFalse(string.IsNullOrEmpty(ToTest.Id));
        }

        [TestMethod]
        public abstract void TestParent();

        [TestMethod]
        public void TestTypedIdChain()
        {
            Assert.IsNotNull(ToTest.TypedIdChain);
            Assert.IsNotNull(ToTest.IdChain);

            string[] elements = ToTest.IdChain.ToArray();
            int elementIdx = 0;

            foreach (var pair in ToTest.TypedIdChain)
            {
                Assert.AreEqual(elements[elementIdx++], pair.Left);
            }
        }

        /// <summary>
        /// Use TestInitialize to run code before running each test .
        /// </summary>
        public virtual void TestInitialize()
        {
            DataContext = DataConnectionFactory.Create(DataAssembly.FromAssembly(typeof(DataContext).Assembly));
            DataObjectSchema = GetSchema(DataContext.Schema);
            Context = DomainContextFactory.Create(DataContext);
            Context.Connect(
                new SyncDataDispatcher(),
                new ConnectionInformationManager(ConnectionInformation.Empty),
                delegate { },
                Resources.GetProjectTestDataFilePath(TestContext.TestDir));

            Context.Projects[Resources.Project2Id].LoadData(new NullDataImportMonitor());
            Context.Projects[Resources.Project1Id].LoadData(new NullDataImportMonitor());
            DataObject = DataContext.Storage[DataObjectSchema].GetById(ToTest.IdChain.ParentChain, ToTest.Id);
        }

        /// <summary>
        /// Use TestCleanup to run code after each test has run.
        /// </summary>
        public virtual void TestCleanup()
        {
            Context.Dispose();
            Context = null;
        }

        /// <summary>
        /// Checks if the parent changed event has been fired after a change of the given collection.
        /// </summary>
        /// <param name="objectToTest">Specifies the object to test.</param>
        /// <param name="newParent">Specifies the new parent instance.</param>
        /// <param name="mustFire">True if the parent changed event must fire.</param>
        protected void TestAssertParentChangedFired(T objectToTest, DomainCollection<T> newParent, bool mustFire)
        {
            int numberOfEvents = (mustFire) ? 1 : 0;
            NotifyPropertyTestStub<T> testStub = new NotifyPropertyTestStub<T>(objectToTest);
            testStub.PropertyName = "Parent";

            using (testStub.Register())
            {
                newParent.Add(objectToTest);

                Assert.AreEqual(numberOfEvents, testStub.PropertyFiredCount);
                Assert.AreEqual(numberOfEvents, testStub.PropertyFiringCount);
            }
        }

        /// <summary>
        /// Gets the default schema of the instance to test.
        /// </summary>
        /// <param name="schema">Specifies the parent schema instance.</param>
        /// <returns>Returns the retrieved schema instance.</returns>
        protected abstract TSchema GetSchema(ScrumTableV1Schema schema);

        /// <summary>
        /// Tests the value change (and the events) of the given Schema/Property.
        /// </summary>
        protected void TestValueChangeWithEventOnDL(SchemaBase propertySchema, object newValue, string propertyName = null)
        {
            DataContext.PropagateEvents = true; // enable events on data layer level

            TestValueChangeWithEvent(
                (propertyName ?? propertySchema.SchemaName),
                () =>
                    {
                        DataObject[propertySchema] = newValue;
                        return newValue;
                    },
                () =>
                    {
                        return DataObject[propertySchema];
                    });
        }

        /// <summary>
        /// Tests the value change (and the events) of the given Schema/Property.
        /// </summary>
        protected void TestValueChangeWithEventOnDM(string propertyName, object newValue)
        {
            TestValueChangeWithEventOnDM(propertyName, newValue, newValue);
        }

        /// <summary>
        /// Tests the value change (and the events) of the given Schema/Property.
        /// </summary>
        protected void TestValueChangeWithEventOnDM(string propertyName, object newValue, object resultValue)
        {
            PropertyInfo getProperty = ToTest.GetType().GetProperty(
                propertyName,
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty);
            PropertyInfo setProperty = ToTest.GetType().GetProperty(
                propertyName,
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty);

            Assert.IsNotNull(getProperty);
            Assert.IsNotNull(setProperty);

            TestValueChangeWithEvent(
                propertyName,
                () =>
                    {
                        setProperty.GetSetMethod().Invoke(ToTest, new[] {newValue});
                        return resultValue;
                    },
                () =>
                    {
                        return getProperty.GetGetMethod().Invoke(ToTest, null);
                    });
        }

        /// <summary>
        /// Tests the value change (and the events) of the given property.
        /// </summary>
        protected void TestValueChangeWithEvent(string dmPropertyName, Func<object> valueSetCall, Func<object> valueGetCall)
        {
            IDictionary<string, string> firingNames = new Dictionary<string, string>();
            IDictionary<string, string> firedNames = new Dictionary<string, string>();

            PropertyChangedEventHandler changedEvent =
                (sender, e) =>
                    {
                        Assert.IsFalse(firedNames.ContainsKey(e.PropertyName));
                        firedNames[e.PropertyName] = e.PropertyName;
                    };

            PropertyChangingEventHandler changingEvent =
                (sender, e) =>
                    {
                        Assert.IsFalse(firingNames.ContainsKey(e.PropertyName));
                        firingNames[e.PropertyName] = e.PropertyName;
                    };


            ToTest.PropertyChanging += changingEvent;
            ToTest.PropertyChanged += changedEvent;

            try
            {
                object valueBefore = valueGetCall();
                object newValue = valueSetCall();

                Assert.AreNotEqual(valueBefore, valueGetCall());
                Assert.AreEqual(newValue, valueGetCall());

                Assert.IsTrue(firedNames.ContainsKey(dmPropertyName));
                Assert.IsTrue(firingNames.ContainsKey(dmPropertyName));
            }
            finally
            {
                ToTest.PropertyChanging -= changingEvent;
                ToTest.PropertyChanged -= changedEvent;
            }
        }

        #endregion

        #region Events

        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------

        #endregion
    }
}
